#pragma once
#include <cpprest/http_client.h>

#define D_PPLX_TASK_VOID_TRY_CATCH(ErrStr) \
try \
{ \
    tk.wait(); \
} \
catch(const web::http::http_exception &exc) \
{ \
    ErrStr = "HTTPClient Connection Exception: error_code="; \
    ErrStr.append(std::to_string(exc.error_code().value())) \
        .append(". ") \
        .append(exc.what()); \
} \
catch(std::exception &exc) \
{ \
    ErrStr = "HTTPClient Connection Exception: "; \
    ErrStr.append(exc.what()); \
} \
catch(...) \
{ \
    ErrStr = "HTTPClient Connection Unknown Exception"; \
}

namespace NSDockerClient {

typedef web::http::http_request httprequest;
typedef web::http::http_response httpresponse;
typedef web::json::value json;
typedef concurrency::streams::istream input_stream;
typedef concurrency::streams::ostream output_stream;

class httpclient
{
public:
    httpclient(const char *url);
    ~httpclient(void) {}
public:
    inline void post(httprequest req, std::function<void(bool, httpresponse, std::string &ErrorString)> funNotify);
    inline bool send(httprequest req, httpresponse outRes, std::string &outErrorString);
private:
    web::http::client::http_client m_httpclient;

public:
    static inline void extract_string(httpresponse res, std::function<void(bool, std::string&, std::string &ErrorString)> funNotify);
    static inline void extract_json(httpresponse res, std::function<void(bool, json&, std::string &ErrorString)> funNotify);
    static inline void extract_binary(httpresponse res, std::function<void(bool, void*, size_t, std::string &ErrorString)> funNotify);
private:
    static inline web::http::client::http_client_config GetHTTPClientConfig(void);
};

inline void httpclient::post(httprequest req, std::function<void(bool, httpresponse, std::string &ErrorString)> funNotify)
{
    m_httpclient.request(req)
        .then([funNotify](web::http::http_response response) {
            std::string ErrorString;
            funNotify(true, response, ErrorString);
        })
        .then([funNotify](pplx::task<void> tk) {
            std::string ErrorString;
            D_PPLX_TASK_VOID_TRY_CATCH(ErrorString)
            if (ErrorString.size())
            {
                web::http::http_response res(400);
                funNotify(false, res, ErrorString);
            }
        });
}

inline bool httpclient::send(httprequest req, httpresponse outRes, std::string &outErrorString)
{
    std::mutex lock;
    std::condition_variable con_var;
    bool bNotify(false), bReqRet;
    this->post(req, [&bReqRet, &outRes, &outErrorString, &lock, &con_var, &bNotify](bool brr, httpresponse res, std::string &ErrStr) {
        bReqRet = brr;
        outRes = res;
        outErrorString = std::move(ErrStr);
        std::lock_guard<std::mutex> _lck(lock);
        bNotify = true;
        con_var.notify_one();
    });
    {
        std::unique_lock<std::mutex> _lck(lock);
        con_var.wait(_lck, [&bNotify]()->bool {
            return bNotify;
        });
    }
    return bReqRet;
}

inline void httpclient::extract_string(httpresponse res, std::function<void(bool, std::string&, std::string &ErrorString)> funNotify)
{
    res.extract_string().then([funNotify](std::string sRes) {
        std::string ErrorString;
        funNotify(true, sRes, ErrorString);
    })
    .then([funNotify](pplx::task<void> tk) {
        std::string ErrorString;
        D_PPLX_TASK_VOID_TRY_CATCH(ErrorString)
        if (ErrorString.size())
        {
            std::string sRes;
            funNotify(false, sRes, ErrorString);
        }
    });
}

inline void httpclient::extract_json(httpresponse res, std::function<void(bool, json&, std::string &ErrorString)> funNotify)
{
    res.extract_json().then([funNotify](json jRes) {
        std::string ErrorString;
        funNotify(true, jRes, ErrorString);
    })
    .then([funNotify](pplx::task<void> tk) {
        std::string ErrorString;
        D_PPLX_TASK_VOID_TRY_CATCH(ErrorString)
        if (ErrorString.size())
        {
            json jRes;
            funNotify(false, jRes, ErrorString);
        }
    });
}

inline void httpclient::extract_binary(httpresponse res, std::function<void(bool, void*, size_t, std::string &ErrorString)> funNotify)
{
    res.extract_vector().then([funNotify](std::vector<unsigned char> vec) {
        std::string ErrorString;
        funNotify(true, vec.data(), vec.size(), ErrorString);
    })
    .then([funNotify](pplx::task<void> tk) {
        std::string ErrorString;
        D_PPLX_TASK_VOID_TRY_CATCH(ErrorString)
        if (ErrorString.size())
        {
            funNotify(false, nullptr, 0, ErrorString);
        }
    });
}

}